/** 深拷贝 */
function deepClone(obj) {
  var clone, property, value;
  if (!obj || typeof obj !== 'object') {
    return obj;
  }
  clone = typeof obj.pop === 'function' ? [] : {};
  clone.__proto__ = obj.__proto__;
  for (property in obj) {
    if (obj.hasOwnProperty(property)) {
      value = obj.property;
      if (value && typeof value === 'object') {
        clone[property] = deepClone(value);
      } else {
        clone[property] = obj[property];
      }
    }
  }
  return clone;
};

/** 坐标点 */
class Point {
  x = 0;
  y = 0;
  constructor(x, y) {
    this.set(x || 0, y || 0);
  }
  set(x, y) {
    this.x = Number(x);
    this.y = Number(y);
  }
  clone() {
    return new Point(this.x, this.y);
  }
  add(point) {
    point = point || new Point();
    return new Point(this.x + (point.x || 0), this.y + (point.y || 0));
  }
  opposite() {
    return new Point(-this.x, -this.y);
  }
}

/** 帧数据 */
class Frame {
  name = '';
  imgName = '';
  offset = new Point();
  size = new Point();
  center = new Point();
  points = new Map(); // <string, Point>
  parent = null; // Frame
  linkName = '';
  constructor(name) {
    this.name = name || '';
  }
  /** 获取绘制位置坐标 */
  getLocation() {
    if (!this.parent) return this.center.opposite();
    let linkLocation = this.getLinkLocation(this.linkName);
    return this.getParentLinkLocation().add(linkLocation);
  }
  /** 获取连接点坐标 */
  getLinkPoint(name) {
    name = name || this.linkName;
    return new Point().add(this.points.get(name));
  }
  /** 获取连接点位置坐标 */
  getLinkLocation(name) {
    return this.getLinkPoint(name).opposite();
  }
  getParentLinkLocation() {
    let pLocation = this.parent.getLocation();
    let pLinkPoint = this.parent.getLinkPoint(this.linkName);
    let pLinkLocation = pLocation.add(pLinkPoint);
    return pLinkLocation;
  }
  clone() {
    return deepClone(this);
  }
}

/** 动作帧数据 */
class ActionFrame {
  name = '';
  class = '';
  type = '';
  constructor(name) {
    this.name = name || '';
  }
}

/** Tenvi客户端XML类 */
class TenviXML {
  url = '';
  type = '';
  xhr = null;
  frames = new Map();
  images = [];
  constructor(url, type) {
    this.url = url || '';
    this.type = type || '';
    if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari
      this.xhr = new XMLHttpRequest();
    } else { // code for IE6, IE5
      this.xhr = new ActiveXObject("Microsoft.XMLHTTP");
    }
    return this;
  }
  /** 载入XML文件 */
  loadXML(callBack) {
    if (!this.xhr) return this;
    let that = this;
    // xmlhttp.addEventListener("load", callBack && callBack(this));
    this.xhr.onload = () => {
      that.loadRes();
      //console.log('TenviXML.loadXML() onload:', that);
      callBack && callBack(that);
    };
    this.xhr.open("GET", this.url, true); // true设置为异步请求
    this.xhr.send();
    return this;
  }
  /** 载入资源：帧数据，图片名数组 */
  loadRes() {
    if (!this.xhr.responseXML) return;
    this.frames.clear();
    let imgSet = new Set();
    let fDoc = this.xhr.responseXML.getElementsByTagName('frame')[0].children;
    for (let i = 0; i < fDoc.length; i++) {
      let f = fDoc[i];
      // 帧基本属性数据
      let fData = new Frame();
      let ddsPath = f.getAttribute('name');
      fData.name = f.tagName;
      fData.imgName = ddsPath.substring(ddsPath.search(/\d{5,}_/), ddsPath.search('.dds'));
      fData.offset.set(f.getAttribute('x'), f.getAttribute('y'));
      fData.size.set(f.getAttribute('sx'), f.getAttribute('sy'));
      fData.center.set(f.getAttribute('cx'), f.getAttribute('cy'));
      // 帧连接点数据
      let pDoc = f.getElementsByTagName('point')[0];
      if (pDoc) {
        for (let j = 0; j < pDoc.children.length; j++) {
          let p = pDoc.children[j];
          fData.points.set(p.getAttribute('name'), new Point(
            p.getAttribute('x'), p.getAttribute('y')));
        }
      }
      this.frames.set(f.tagName, fData);
      imgSet.add(fData.imgName);
    }
    this.images = [...imgSet];
  }
  /** 获取帧数据 */
  getFrames(regex) {
    let framesMap = new Map();
    this.frames.forEach((value, key) => {
      if (regex && !regex.test(key)) return;
      framesMap.set(key, value);
    });
    return framesMap;
  }
}

/** 角色类 */
class Hero {
  race = 'Andras'; // Andras, Silva, Talli
  guard = '';
  part = { // parent, link, type, regex, list
    body: { parent: null, link: 'center', type: 'bd', regex: /.*_body$/ },
    arm: { parent: 'body', link: 'arm', type: 'bd', regex: /.*_arm$/ },
    leg: { parent: 'body', link: 'leg', type: 'bd', regex: /.*_leg$/ },
    weapon: { parent: 'body', link: 'wp', type: 'wp' },
    clothes: { parent: 'body', link: 'neck', type: 'cl', regex: /.*_body$/ },
    sleeve: { parent: 'clothes', link: 'psleeve', type: 'cl', regex: /.*_arm$/ },
    pants: { parent: 'clothes', link: 'pleg', type: 'cl', regex: /.*_leg$/ },
    wing: { parent: 'body', link: 'neck', type: 'cl', regex: /.*_wing$/ },
    head: { parent: 'body', link: 'neck', type: 'hd', regex: /head.*/ },
    horn: { parent: 'head', link: 'brow', type: 'hd', regex: /horn.*_0$/ },
    horn1: { parent: 'head', link: 'brow', type: 'hd', regex: /horn.*_1$/ },
    ear: { parent: 'head', link: 'brow', type: 'hd', regex: /ear.*_0$/ },
    ear1: { parent: 'head', link: 'brow', type: 'hd', regex: /ear.*_1$/ },
    face: { parent: 'head', link: 'brow', type: 'fc' },
    facial: { parent: 'head', link: 'brow', type: 'fa', regex: /^((?!.*(_01)).)*$/ },
    facial1: { parent: 'head', link: 'brow', type: 'fa', regex: /.*_01$/ },
    hair: { parent: 'head', link: 'brow', type: 'hr', regex: /hair(?!Lower).*/ },
    hair1: { parent: 'head', link: 'brow', type: 'hr', regex: /hairLower.*/ },
    cap: { parent: 'head', link: 'brow', type: 'cp' },
    //emo: { parent: 'head', link: 'brow', type: 'emo' },
    Abody: { parent: null, link: 'center', type: 'Adf', regex: /^body_.*/ },
    Aarml: { parent: 'Abody', link: 'arml', type: 'Adf', regex: /^arml_.*/ },
    Aarmr: { parent: 'Abody', link: 'armr', type: 'Adf', regex: /^armr_.*/ },
  }
  order = [
    'sleeve', 'arm', 'weapon', 'cap', 'hair',
    'facial', 'emo', 'face',
    'horn', 'ear', 'head', 'facial1', 'ear1', 'horn1',
    'pants', 'leg', 'clothes', 'body', 'hair1', 'wing'
  ];
  constructor(race, guard) {
    this.race = race || 'Andras';
    this.guard = guard || '';
  }
  /** 获取一个动作的所有部件帧 */
  getActionFrames(heroAction, frameMap) {
    //console.log('hero.getActionFrames()', heroAction, frameMap);
    let that = this;
    let parts = {};
    Object.keys(this.part).map(key => {
      if (!heroAction[key]) return;
      let thisPart = that.part[key];
      let frameKey = `hero-${thisPart.type}:${heroAction[key]}`;
      parts[key] = frameMap.get(frameKey);
      if (parts[key]) {
        parts[key].parent = parts[thisPart.parent] || null;
        parts[key].linkName = thisPart.link || '';
      }
      //console.log(key, frameKey, parts[key]);
    });
    let frames = [];
    //let order = this.order[this.race] || [];
    this.order.reverse().map(item => {
      if (parts[item.name]) frames.push(parts[item.name]);
    });
    return frames;
  }
  /** 装载名称到部件帧名称列表 */
  addPartFrameName(frameName, typeName) {
    if (!frameName || !typeName) return;
    let part = this.part;
    Object.keys(part).map(key => {
      if (part[key]['type'] !== typeName) return;
      if (part[key]['regex'] && !part[key]['regex'].test(frameName)) return;
      if (!part[key]['list']) part[key]['list'] = [];
      part[key]['list'].push(frameName);
      //console.log('hero.addPartFrameName()', frameName, typeName, part[key]);
    });
  }
  /** 重置部件帧名称列表 */
  resetPartFrameName(typeName) {
    let that = this;
    Object.keys(this.part).map(key => {
      if (typeName && that.part[key].type !== typeName) return;
      that.part[key]['list'] = [];
    });
  }
}

/** Excel数据类
 *  依赖：lib/xlsx.full.min.js
 */
class ExcelData {
  url = '';
  workbook = null;
  isLoaded = false;
  constructor(url) {
    url && this.load(url);
    return this;
  }
  loadPromise(url) {
    let that = this;
    return new Promise((resolve, reject) => {
      that.load(url, resolve);
    });
  }
  load(url, callBack) {
    let that = this;
    let req = new XMLHttpRequest();
    req.open("GET", url, true);
    req.responseType = "arraybuffer";
    req.onload = function (e) {
      let data = new Uint8Array(req.response);
      that.workbook = XLSX.read(data, { type: "array" });
      that.url = url;
      that.isLoaded = true;
      //console.log('ExcelData.load(' + url + ')', that.workbook);
      callBack && callBack(that);
    }
    req.send();
    return this;
  }
  getJson(sheetName) {
    if (!sheetName) {
      sheetName = this.workbook.SheetNames[0];
    }
    let ws = this.workbook.Sheets[sheetName];
    return XLSX.utils.sheet_to_json(ws);
  }
}
